#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <math.h>

#include <iostream>
#include <fstream>

#include "zlib.h"

#include "libraries.h"
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
#define LINGOLENGTH  4
#define LENGTHARRAY  40096
#define CUTOFFFREQ   1
#define ORIGINALFREQ 0
#define CHANGEDIGIT  '?'
#define EUCLIDIAND   1
#define POSITIONL    1
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
using namespace std;
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
struct LingoFreq {

  char  *lingo;
  int    frequency;

  LingoFreq() {
    this->lingo     = NULL;
    this->frequency = 0;
  }

  ~LingoFreq() {
    if (lingo) delete[] lingo;
  }

  struct LingoFreq operator=(const struct LingoFreq &lingoFreq) {

    if (this->lingo) delete[] this->lingo;
    this->lingo = new char[strlen(lingoFreq.lingo) + 1];
    strcpy(this->lingo, lingoFreq.lingo);

    this->frequency = lingoFreq.frequency;

    return *this;

  };

};

#define LINGOFreq struct LingoFreq

struct fileLingoFreq {

    char *name;
    int   lengthLingo;

    fileLingoFreq() {
      name        = NULL;
      lengthLingo = 0;
    };
    ~fileLingoFreq() {
      if (name) delete[] name;
    };

  };

#define FileLingoFreq struct fileLingoFreq

struct SmilesRef {

  char  *smiles;
  char  *ref;

  SmilesRef() {
    this->smiles = NULL;
    this->ref    = NULL;
  }

  ~SmilesRef() {
    if (smiles) delete[] smiles;
    if (ref)    delete[] ref;
  }

  struct SmilesRef operator=(const struct SmilesRef &smilesRef) {

    if (this->smiles) delete[] this->smiles;
    this->smiles = new char[strlen(smilesRef.smiles) + 1];
    strcpy(this->smiles, smilesRef.smiles);

    if (this->ref) delete[] this->ref;
    this->ref = new char[strlen(smilesRef.ref) + 1];
    strcpy(this->ref, smilesRef.ref);

    return *this;

  };

};

#define SMILESRef struct SmilesRef

struct DistanceRef {

  char  *ref;
  double distance;
  int    cumulative;
  int    numberLingoLib;
  int    position;
  double  averLingoSim;

  DistanceRef() {
    this->ref            = NULL;
    this->distance       = 0;
    this->cumulative     = 0;
    this->numberLingoLib = 0;
    this->position       = 0;
    this->averLingoSim   = 0;
  }

  ~DistanceRef() {
    if (ref) delete[] ref;
  }

  struct DistanceRef operator=(const struct DistanceRef &distanceRef) {

    if (this->ref) delete[] this->ref;
    this->ref = new char[strlen(distanceRef.ref) + 1];
    strcpy(this->ref, distanceRef.ref);

    this->distance       = distanceRef.distance;
    this->cumulative     = distanceRef.cumulative;
    this->numberLingoLib = distanceRef.numberLingoLib;
    this->position       = distanceRef.position;
    this->averLingoSim   = distanceRef.averLingoSim;

    return *this;

  };

};

#define DISTANCERef struct DistanceRef

//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
namespace LingoSpace {

  bool FindLingo(TNode <LINGOFreq>* NodeT, void* ref = NULL) {
    return (!strcmp(NodeT->Info()->lingo, (char*)ref))?true:false;
  }

  bool MajorFreq(TNode <LINGOFreq>* NodeT, void* ref) {
    int FreqA = NodeT->Info()->frequency;
    int FreqB = ((TNode <LINGOFreq>*)ref)->Info()->frequency;
    return (FreqA > FreqB)?true:false;
  }

  bool AlphLingo(TNode <LINGOFreq>* NodeT, void* ref) {
    char *LingoA = NodeT->Info()->lingo;
    char *LingoB = ((TNode <LINGOFreq>*)ref)->Info()->lingo;
    return (strcmp(LingoA, LingoB) > 0)?true:false;
  }

  bool ContainLingoInit(TNode <LINGOFreq>* NodeT, void* ref = NULL) {
    printf("LINGO Compare %s Refe %s\n", NodeT->Info()->lingo, (char*)ref);

    return (strstr(NodeT->Info()->lingo, (char*)ref))?true:false;
  }

  bool ContainLingoEnd(TNode <LINGOFreq>* NodeT, void* ref = NULL) {
    printf("LINGO Compare %s\n", NodeT->Info()->lingo);
    return (!strcmp((NodeT->Info()->lingo + 1), (char*)ref))?true:false;
  }

  int ContainLingoInitS(TNode <LINGOFreq>* NodeT, void* ref = NULL) {
    //printf("LINGO Compare %s Refe %s\n", (((LINGOFreq*)NodeT->Info())->lingo), (char*)ref);
    if (strcmp(NodeT->Info()->lingo, (char*)ref) < 0) return -1;
    return (strstr(NodeT->Info()->lingo, (char*)ref))?1:0;
  }

  int ContainLingoEndS(TNode <LINGOFreq>* NodeT, void* ref = NULL) {
    //if (strcmp((((LINGOFreq*)NodeT->Info())->lingo + 1), (char*)ref) < 0) return -1;
    return (!strcmp((NodeT->Info()->lingo + 1), (char*)ref))?1:0;
  }
};
namespace LibrarySpace {
  bool FindRef(TNode <SMILESRef>* NodeT, void* ref = NULL) {
    return (!strcmp(NodeT->Info()->ref, (char*)ref))?true:false;
  }
};
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
double obtainLingoSim(char *lingoString1, char *lingoString2) {

  if (strlen(lingoString1) < LINGOLENGTH || strlen(lingoString2) < LINGOLENGTH) return 0;

  int length1 = strlen(lingoString1), length2 = strlen(lingoString2);
  char tmpLingo1[length1], tmpLingo2[length2];
  int  i, j, k, z;

  //Change the Cl bu L and Br by R in the two lingo sequences
  for (i = 0, j = 0, k = 0; i < ((length1 > length2)?length1:length2); i++) {
    if (i < length1) {
      if      ((lingoString1[i] == 'l') || (lingoString1[i] == 'r')) tmpLingo1[j - 1] = (lingoString1[i] == 'l')?'L':'R';
      //else if (isdigit(lingoString1[i])) tmpLingo1[j++] = '0';
      else tmpLingo1[j++] = lingoString1[i];
    }
    if (i < length2) {
      if ((lingoString2[i] == 'l') || (lingoString2[i] == 'r')) tmpLingo2[k - 1] = (lingoString2[i] == 'l')?'L':'R';
      //else if (isdigit(lingoString2[i])) tmpLingo2[k++] = '0';
      else tmpLingo2[k++] = lingoString2[i];
    }
  }
  tmpLingo1[j] = '\0'; tmpLingo2[k] = '\0';

  j -= (LINGOLENGTH - 1); k -= (LINGOLENGTH - 1);
  int    L = j + k; //Total Lingos in two sequences
  double similarityValue = 0, Nai, Nbi;

  for (i = 0; i < j + k; i++) {

    char   compare[5];
    bool   analize = true;

    Nai = 0, Nbi = 0;

    if (i < j) {

      strncpy(compare, tmpLingo1 + i, 4);
      compare[4] = '\0';

      for (z = 0; z < ((j > k)?j:k); z++) {
        if (z < j && !strncmp(compare, tmpLingo1 + z, 4)) {
          if (z < i) {
            analize = false;
            break;
          }
          Nai++;
        }
        if (z < k && !strncmp(compare, tmpLingo2 + z, 4)) {
          Nbi++;
        }
      }
    }
    else {

      strncpy(compare, tmpLingo2 + i - j, 4);
      compare[4] = '\0';

      for (z = 0; z < ((j > k)?j:k); z++) {
        if (z < j && !strncmp(compare, tmpLingo1 + z, 4)) {
          analize = false;
          break;
        }
        if (z < k && !strncmp(compare, tmpLingo2 + z, 4)) {
          if (z < i - j) {
            analize = false;
            break;
          }
          Nbi++;
        }
      }
    }

    if (analize == true) similarityValue += (((Nai - Nbi) >= 0)?(1.0 - ((double)(Nai - Nbi)/(double)(Nai + Nbi))):(1 - ((double)(Nbi - Nai)/(double)(Nai + Nbi))));
    else                 L--;
  }

  return (double)((double)similarityValue/(double)L);

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
double obtainLingoSimGeneral(char *lingoString1, char *lingoString2) {

  if (strlen(lingoString1) < LINGOLENGTH || strlen(lingoString2) < LINGOLENGTH) return 0;

  int length1 = strlen(lingoString1), length2 = strlen(lingoString2);
  char tmpLingo1[length1], tmpLingo2[length2];
  int  i, j, k, z;

  //Change the Cl bu L and Br by R in the two lingo sequences
  for (i = 0, j = 0, k = 0; i < ((length1 > length2)?length1:length2); i++) {
    if (i < length1) {
      if (isdigit(lingoString1[i]) || (lingoString1[i] == '%')) tmpLingo1[j++] = '?';
      else tmpLingo1[j++] = lingoString1[i];
    }
    if (i < length2) {
      if (isdigit(lingoString2[i]) || (lingoString2[i] == '%')) tmpLingo2[k++] = '?';
      else tmpLingo2[k++] = lingoString2[i];
    }
  }
  tmpLingo1[j] = '\0'; tmpLingo2[k] = '\0';

  j -= (LINGOLENGTH - 1); k -= (LINGOLENGTH - 1);
  int    L = j + k; //Total Lingos in two sequences
  double similarityValue = 0, Nai, Nbi;

  for (i = 0; i < j + k; i++) {

    char   compare[5];
    bool   analize = true;

    Nai = 0, Nbi = 0;

    if (i < j) {

      strncpy(compare, tmpLingo1 + i, 4);
      compare[4] = '\0';

      for (z = 0; z < ((j > k)?j:k); z++) {
        if (z < j && !strncmp(compare, tmpLingo1 + z, 4)) {
          if (z < i) {
            analize = false;
            break;
          }
          Nai++;
        }
        if (z < k && !strncmp(compare, tmpLingo2 + z, 4)) {
          Nbi++;
        }
      }
    }
    else {

      strncpy(compare, tmpLingo2 + i - j, 4);
      compare[4] = '\0';

      for (z = 0; z < ((j > k)?j:k); z++) {
        if (z < j && !strncmp(compare, tmpLingo1 + z, 4)) {
          analize = false;
          break;
        }
        if (z < k && !strncmp(compare, tmpLingo2 + z, 4)) {
          if (z < i - j) {
            analize = false;
            break;
          }
          Nbi++;
        }
      }
    }

    if (analize == true) similarityValue += (((Nai - Nbi) >= 0)?(1 - ((Nai - Nbi)/(Nai + Nbi))):(1 - ((Nbi - Nai)/(Nai + Nbi))));
    else                 L--;
  }

  return similarityValue/L;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void trim(char *stream) {

  int i, j;
  for (i = 0, j = 0; i < strlen(stream); i++)
    if ((stream[i] != '\n') && (stream[i] != '\r'))
        stream[j++] = stream[i];
  stream[j] = '\0';

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
char **GetAllFiles(const char *path, int &numberFiles) {

  DIR     *dir = opendir(path);
  struct   dirent *dp;             /* returned from readdir() */
  size_t   filecount = 0;          /* number of entries in directory */
  size_t   i = 0;
  char   **files;

  if (dir == NULL) return NULL;    /* opendir() failed */

  /* Pass 1: Count number of files and subdirectories */
  while ((dp = readdir(dir)) != NULL) filecount++;

  numberFiles = filecount;

  /* Allocate space for the result */
  files = (char **)malloc(filecount * sizeof(*files));
  if (files == NULL) return NULL;  /* malloc failed */

  /* Pass 2: Collect all filenames */
  rewinddir (dir);
  while ((dp = readdir (dir)) != NULL) {
   files[i] = strdup (dp->d_name);
   if (files[i] == NULL) {
      /* memory allocation failure; free what's been allocated
       * so far and return NULL.*/
      while (i > 0) free (files[--i]);

      free (files);
      return NULL;
    }
    i++;
  }

  closedir (dir);
  return files;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
char *extractElement(char *lines, int position) {

  if (position < 1) return NULL;
  char *pch = strtok (lines,", \n");
  if (position == 1) return pch;
  int intPos = 1;
  while (pch != NULL) {
    pch = strtok (NULL, ", \n");
    if (++intPos == position) return pch;
  }
  return NULL;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
char* splitPosLine(char *line, int pos, char character) {

  if (pos < 0) return NULL;

  int   count = 1, length = 0;
  char *tmpResult = new char[strlen(line) + 1];

  for (int i = 0; i < strlen(line); i++) {
    if (line[i] == character) { count++; i++; }
    if (pos == count) tmpResult[length++] = line[i];
  }
  tmpResult[length] = '\0';

  char *result = new char[length + 1];
  strcpy(result, tmpResult);
  result[length] = '\0';
  delete[] tmpResult;

  return result;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void changeBondCharacter(char **molecule) {

  if (!molecule || !*molecule ) return;
  for (int i = 0; i < strlen(*molecule); i++)
    if (isdigit((*molecule)[i]))
      (*molecule)[i] = CHANGEDIGIT;


}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int numberRepetition(char *sequence, char *lingo) {

  if (!sequence || !lingo) return -1;

  int   count = 0;
  char *tmpSeq = sequence;
  changeBondCharacter(&tmpSeq);
  while ((tmpSeq = strstr(tmpSeq, lingo)) != NULL) {
    tmpSeq = tmpSeq + 1; count++;
  }

  return count;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int main(int argc, char *argv[]) {

  char  *nameSeqFile = NULL, *nameOutFile = NULL;

  if (argc < 3) {
    cout << "Error in the input parameters! Must by like: " << endl;
    cout << "./ComputeLingoSimMatrix <Input File> <Output File>" << endl;
    return 0;
  }
  else {
    nameSeqFile = new char[strlen(argv[1]) + 1];
    strcpy(nameSeqFile, argv[1]);
    nameOutFile = new char[strlen(argv[2]) + 1];
    strcpy(nameOutFile, argv[2]);
  }

  //Read the configuration File
  if (!nameSeqFile || !nameOutFile) {
    if (nameSeqFile)   delete[] nameSeqFile;
    if (nameOutFile)   delete[] nameOutFile;
    return 0;
  }

  int                 ratio = 0;
  char*               buffer           = new char[LENGTHARRAY + 1];

  TListE <SMILESRef>* listSequencesRef = new TListE <SMILESRef> ();
  gzFile              sequenceFile     = gzopen(nameSeqFile,   "rb9");

  while (!gzeof(sequenceFile)) {

    gzgets(sequenceFile, buffer, LENGTHARRAY);
    if (!buffer) continue;

    trim(buffer);
    SMILESRef *tmpSmilesRef = new SMILESRef();

    tmpSmilesRef->smiles = splitPosLine(buffer, 1, ' '); trim(tmpSmilesRef->smiles);
    tmpSmilesRef->ref    = splitPosLine(buffer, 2, ' '); trim(tmpSmilesRef->ref);

    //printf(" - %s %s\n", tmpSmilesRef->ref, tmpSmilesRef->smiles);

    listSequencesRef->Add(tmpSmilesRef);

  }

  gzclose(sequenceFile);

  gzFile outFile = gzopen(nameOutFile,   "wb9");

  for (int i = 0; i < listSequencesRef->Length(); i++) {
    gzprintf(outFile, "%8s ", listSequencesRef->Get(i)->ref);
    //printf("%8s ", listSequencesRef->Get(i)->ref);
  }
  gzprintf(outFile, "\n");
  //printf("\n");

  for (int i = 0; i < listSequencesRef->Length(); i++) {
    for (int j = 0; j < listSequencesRef->Length(); j++) {
      char   *smiles1 = listSequencesRef->Get(i)->smiles;
      char   *smiles2 = listSequencesRef->Get(j)->smiles;
      double  lingoSim = obtainLingoSim(smiles1, smiles2);
      gzprintf(outFile, "%08f ", lingoSim);
      //printf("%s - %s -> LingoSim = %08f \n", listSequencesRef->Get(i)->smiles, listSequencesRef->Get(j)->smiles , lingoSim);
    }
    gzprintf(outFile, "\n");
    printf("[%d]\n", i);
  }

  gzclose(outFile);

  if (listSequencesRef) delete   listSequencesRef;
  if (nameSeqFile)      delete[] nameSeqFile;
  if (nameOutFile)      delete[] nameOutFile;
  if (buffer)           delete[] buffer;

  return 1;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//

